package nz.ac.massey.cs.plot;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;

import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.UIManager;

public abstract class AbstractPlot extends JComponent implements Plot {
	
	private String tag;
    private char plotChar;
    private Font plotFont, titleFont, xyLabelFont, xIntervalFont, yIntervalFont;
    private int yClasses, xClasses;
    private double xAxisStart, yAxisStart,
            titleHeight, ymax, ymin, xmax, xmin, yOffset, xOffset;
    private Stroke normalStroke, titleStroke, originStroke, dashedStroke;
    private String xLabel = "x", yLabel = "y", title = "Plot of ...";
    private boolean showXGridLines, showYGridLines;
    private Color titleColor, originAxisColor, equationColor,
            regressionLineColor, backgroundColor, plotColor, lineColor,
            gridLineColor, intervalColor, xyLabelColor;

    @Override
    public boolean isShowXGridLines() {
        return showXGridLines;
    }

    @Override
    public void setShowXGridLines(boolean showXGridLines) {
        this.showXGridLines = showXGridLines;
    }

    @Override
    public boolean isShowYGridLines() {
        return showYGridLines;
    }

    @Override
    public void setShowYGridLines(boolean showYGridLines) {
        this.showYGridLines = showYGridLines;
    }

    public AbstractPlot() {
        super();
        setSize(240, 260);

        String fntName = UIManager.getDefaults().getFont("TabbedPane.font").getName();

        xIntervalFont = yIntervalFont = new Font(fntName, Font.PLAIN, 9);
        titleFont = new Font(fntName, Font.BOLD, 15);
        xyLabelFont = new Font(fntName, Font.PLAIN, 11);

        ymin = xmin = Double.MIN_VALUE;
        ymax = xmax = Double.MAX_VALUE;

        dashedStroke = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{3}, 0);
        originStroke = new BasicStroke(1.5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{1}, 0);
        titleStroke = new BasicStroke(2.2f);
        normalStroke = new BasicStroke(1.0f);
        titleHeight = 40;

        xOffset = 25;
        yOffset = 35;
        plotChar = '@';

        xAxisStart = xOffset + 25;
        yAxisStart = titleHeight;

        xClasses = 8;
        yClasses = 8;

        intervalColor = Color.BLACK;
        lineColor = Color.BLACK;
        titleColor = Color.BLACK;
        xyLabelColor = Color.BLACK;
        backgroundColor = Color.WHITE;

        plotColor = Color.BLACK;
        regressionLineColor = Color.RED;
        gridLineColor = Color.GRAY;
        originAxisColor = Color.ORANGE;
        equationColor = Color.BLACK;

        showYGridLines = true;
    }

    @Override
    /**
     * saves the current histogram as a png image
     *
     * @param path the image file path
     * @throws IOException if the path is incorrect or not writable
     */
    public void toPNG(String path) throws IOException {
        javax.imageio.ImageIO.write(getImage(), "png", new File(path));
    }

    public BufferedImage getImage() {
    	redraw(getGraphics());
        BufferedImage bimage = new BufferedImage(getHeight(), getWidth(), BufferedImage.TYPE_INT_ARGB);
        paint(bimage.getGraphics());
        return bimage;
    }

    @Override
    public ImageIcon getImageIcon() {
        return new ImageIcon(getImage());
    }

    @Override
    public void paintComponent(Graphics graphics) {
        redraw(graphics);
    }

    @Override
    public void paint(Graphics graphics) {
        redraw(graphics);
    }

    @Override
    public Stroke getOriginStroke() {
        return this.originStroke;
    }

    @Override
    public Color getOriginAxisColor() {
        return originAxisColor;
    }

    @Override
    public Color getEquationColor() {
        return equationColor;
    }

    @Override
    public Color getRegressionLineColor() {
        return regressionLineColor;
    }

    @Override
    public Color getBackgroundColor() {
        return backgroundColor;
    }

    @Override
    public Color getPlotColor() {
        return plotColor;
    }

    @Override
    public char getPlotChar() {
        return plotChar;
    }

    @Override
    public Color getLineColor() {
        return lineColor;
    }

    @Override
    public Font getPlotFont() {
        return plotFont;
    }

    @Override
    public Stroke getDashedStroke() {
        return dashedStroke;
    }

    @Override
    public double getYClasses() {
        return yClasses;
    }

    @Override
    public Color getGridLineColor() {
        return gridLineColor;
    }

    @Override
    public int getXClasses() {
        return xClasses;
    }

    @Override
    public Color getIntervalColor() {
        return intervalColor;
    }

    @Override
    public double getxClassWidth() {
        return (getxAxisEnd() - getxAxisStart()) / getxClasses();
    }

    @Override
    public double getyClassHeight() {
        return (getyAxisEnd() - getyAxisStart()) / getyClasses();
    }

    @Override
    public Stroke getNormalStroke() {
        return normalStroke;
    }

    @Override
    public double getTitleHeight() {
        return titleHeight;
    }

    @Override
    public Color getXYLabelColor() {
        return xyLabelColor;
    }

    @Override
    public Color getTitleColor() {
        return titleColor;
    }

    @Override
    public int getyClasses() {
        return yClasses;
    }

    @Override
    public void setyClasses(int yClasses) {
        this.yClasses = yClasses;
    }

    @Override
    public int getxClasses() {
        return xClasses;
    }

    @Override
    public void setxClasses(int xClasses) {
        this.xClasses = xClasses;
    }

    @Override
    public int gethOffset() {
        return (int) (getHeight() - titleHeight);
    }

    @Override
    public double getxAxisEnd() {
        return getWidth() - xAxisStart + 20;
    }

    @Override
    public double getyAxisEnd() {
        return gethOffset() - 5;
    }

    @Override
    public double getxAxisStart() {
        return xAxisStart;
    }

    @Override
    public void setxAxisStart(double xAxisStart) {
        this.xAxisStart = xAxisStart;
    }

    @Override
    public double getyAxisStart() {
        return yAxisStart;
    }

    @Override
    public void setyAxisStart(double yAxisStart) {
        this.yAxisStart = yAxisStart;
    }

    @Override
    public Stroke getTitleStroke() {
        return titleStroke;
    }

    @Override
    public void setTitleStroke(Stroke titleStroke) {
        this.titleStroke = titleStroke;
    }

    @Override
    public String getxLabel() {
        return xLabel;
    }

    @Override
    public void setxLabel(String xLabel) {
        this.xLabel = xLabel;
    }

    @Override
    public String getyLabel() {
        return yLabel;
    }

    @Override
    public void setyLabel(String yLabel) {
        this.yLabel = yLabel;
    }

    @Override
    public String getTitle() {
        return title;
    }

    @Override
    public void setTitle(String title) {
        this.title = title;
    }

    @Override
    public Color getXyLabelColor() {
        return xyLabelColor;
    }

    @Override
    public void setXyLabelColor(Color xyLabelColor) {
        this.xyLabelColor = xyLabelColor;
    }

    @Override
    public void setPlotChar(char plotChar) {
        this.plotChar = plotChar;
    }

    @Override
    public void setPlotFont(Font plotFont) {
        this.plotFont = plotFont;
    }

    @Override
    public void setTitleHeight(double titleHeight) {
        this.titleHeight = titleHeight;
    }

    @Override
    public void setNormalStroke(Stroke normalStroke) {
        this.normalStroke = normalStroke;
    }

    @Override
    public void setOriginStroke(Stroke originStroke) {
        this.originStroke = originStroke;
    }

    @Override
    public void setDashedStroke(Stroke dashedStroke) {
        this.dashedStroke = dashedStroke;
    }

    @Override
    public void setTitleColor(Color titleColor) {
        this.titleColor = titleColor;
    }

    @Override
    public void setOriginAxisColor(Color originAxisColor) {
        this.originAxisColor = originAxisColor;
    }

    @Override
    public void setEquationColor(Color equationColor) {
        this.equationColor = equationColor;
    }

    @Override
    public void setRegressionLineColor(Color regressionLineColor) {
        this.regressionLineColor = regressionLineColor;
    }

    @Override
    public void setBackgroundColor(Color backgroundColor) {
        this.backgroundColor = backgroundColor;
    }

    @Override
    public void setPlotColor(Color plotColor) {
        this.plotColor = plotColor;
    }

    @Override
    public void setLineColor(Color lineColor) {
        this.lineColor = lineColor;
    }

    @Override
    public void setGridLineColor(Color gridLineColor) {
        this.gridLineColor = gridLineColor;
    }

    @Override
    public void setIntervalColor(Color intervalColor) {
        this.intervalColor = intervalColor;
    }

    @Override
    public double getYmax() {
        return ymax;
    }

    @Override
    public void setYmax(double ymax) {
        this.ymax = ymax;
    }

    @Override
    public double getYmin() {
        return ymin;
    }

    @Override
    public void setYmin(double ymin) {
        this.ymin = ymin;
    }

    @Override
    public double getXmax() {
        return xmax;
    }

    @Override
    public void setXmax(double xmax) {
        this.xmax = xmax;
    }

    @Override
    public double getXmin() {
        return xmin;
    }

    @Override
    public void setXmin(double xmin) {
        this.xmin = xmin;
    }

    @Override
    public double getyOffset() {
        return yOffset;
    }

    @Override
    public void setyOffset(double yOffset) {
        this.yOffset = yOffset;
    }

    @Override
    public double getxOffset() {
        return xOffset;
    }

    @Override
    public void setxOffset(double xOffset) {
        this.xOffset = xOffset;
    }

    @Override
    public void setTitleFont(Font titleFont) {
        this.titleFont = titleFont;
    }

    @Override
    public Font getTitleFont() {
        return titleFont;
    }

    @Override
    public void drawTitle(Graphics2D g) {
        g.setStroke(getTitleStroke());
        g.setColor(getTitleColor());
        g.setFont(getTitleFont());
        g.drawString(title, (getWidth() - g.getFontMetrics().stringWidth(title)) / 2, (int) (getTitleHeight() / 2));
    }

    @Override
    public void setXyLabelFont(Font xyLabelFont) {
        this.xyLabelFont = xyLabelFont;
    }

    @Override
    public Font getXyLabelFont() {
        return xyLabelFont;
    }

    @Override
    public void drawYLabel(Graphics2D g) {
        g.setFont(getXyLabelFont());
        g.setColor(getXYLabelColor());
        int w = g.getFontMetrics().stringWidth(yLabel);
        g.rotate(-Math.PI / 2);
        g.drawString(yLabel, (-getHeight() - w) / 2, 15);
        g.rotate(Math.PI / 2);
    }

    @Override
    public void drawXLabel(Graphics2D g) {
        g.setFont(getXyLabelFont());
        g.setColor(getXYLabelColor());
        g.drawString(xLabel, (getWidth() - g.getFontMetrics().stringWidth(xLabel)) / 2, getHeight() - 7);
    }

    @Override
    public void drawYAxis(Graphics2D g) {
        g.setColor(getLineColor());
        g.draw(new Line2D.Double(getxAxisStart() - 10,
                getyAxisStart() - 5,
                getxAxisStart() - 10,
                getyAxisEnd() - 5));
    }

    @Override
    public void drawXAxis(Graphics2D g) {
        g.setColor(getLineColor());
        g.draw(new Line2D.Double(getxAxisStart(), gethOffset(), getxAxisEnd(), gethOffset()));
    }

    @Override
    public Font getxIntervalFont() {
        return xIntervalFont;
    }

    @Override
    public void setxIntervalFont(Font xIntervalFont) {
        this.xIntervalFont = xIntervalFont;
    }

    @Override
    public Font getyIntervalFont() {
        return yIntervalFont;
    }

    @Override
    public void setyIntervalFont(Font yIntervalFont) {
        this.yIntervalFont = yIntervalFont;
    }

    @Override
    public void drawYIntervals(Graphics2D g) {

        double y = getYmax();
        double ypos = getyAxisStart() - 5;
        double y_scaled_interval = (getYmax() - getYmin()) / getYClasses();

        g.setFont(getyIntervalFont());

        for (int i = 0; i <= getYClasses(); i++) {
            g.setColor(getLineColor());
            g.draw(new Line2D.Double(getxAxisStart() - 15, ypos, getxAxisStart() - 10, ypos));
            if (isShowYGridLines()) {
                g.setStroke(getDashedStroke());
                g.setColor(getGridLineColor());
                g.draw(new Line2D.Double(getxAxisStart(), ypos, getxAxisEnd(), ypos));
            }
            g.setStroke(getNormalStroke());
            if (i % 2 == 0) {
                g.setColor(getIntervalColor());

                g.rotate(-Math.PI / 2);

                String format = "0.00";
                if (y > 9999) {
                    format = "0.#E0";
                }
                DecimalFormat formatter = new DecimalFormat(format);
                String str = formatter.format(y);
                if (str.endsWith(".00")) {
                    str = str.substring(0, str.length() - 3);
                }
                if (str.equals("-0.00") || str.equals("-0")) {
                    str = "0";
                }
                int ln = g.getFontMetrics().stringWidth(str);
                g.drawString(str, (int) (-ypos - (ln / 2)), 30);
                g.rotate(Math.PI / 2);

            }
            ypos += getyClassHeight();
            y -= y_scaled_interval;
        }

        g.setColor(getLineColor());
        g.draw(new Line2D.Double(getxAxisStart() - 15, getyAxisEnd() - 5, getxAxisStart() - 10, getyAxisEnd() - 5));
    }

    @Override
    public void drawBackground(Graphics2D g) {
        g.setColor(getBackgroundColor());
        g.fillRect(0, 0, getWidth(), getHeight());
    }

    @Override
    public void drawXIntervals(Graphics2D g) {
        String str;
        DecimalFormat formatter;
        double xpos = getxAxisEnd();
        double x = getXmax();
        double x_scaled_interval = (getXmax() - getXmin()) / getXClasses();

        g.setFont(getxIntervalFont());

        for (int i = 0; i <= getXClasses(); i++) {
            g.setColor(getLineColor());
            g.draw(new Line2D.Double(xpos, gethOffset(), xpos, gethOffset() + 5));
            if (isShowXGridLines()) {
                g.setStroke(getDashedStroke());
                g.setColor(getGridLineColor());
                g.draw(new Line2D.Double(xpos, gethOffset(), xpos, getTitleHeight()));
            }
            g.setStroke(getNormalStroke());
            if (i % 2 == 0) {
                String format = "0";
                if (x < 1) {
                    format = "0.0";
                } else if (x > 9999) {
                    format = "0.#E0";
                }
                formatter = new DecimalFormat(format);
                g.setColor(getIntervalColor());
                str = formatter.format(x);
                if (str.endsWith(".0")) {
                    str = str.substring(0, str.length() - 2);
                }
                if (str.equals("-0.0") || str.equals("-0")) {
                    str = "0";
                }
                if (str.indexOf('.') > -1 && getXmax() > 1) {
                    str = str.substring(0, str.indexOf('.'));
                }
                int ln = g.getFontMetrics().stringWidth(str);
                g.drawString(str, (int) (xpos - (ln / 2.0)), gethOffset() + 20);

            }
            xpos -= getxClassWidth();
            x -= x_scaled_interval;
        }

//        g.setStroke(getNormalStroke());
//        String format = "0";
//        if (x < 1) {
//            format = "0.0";
//        } else if (getXmin() > 999) {
//            format = "0.#E0";
//        }
//
//        formatter = new DecimalFormat(format);
//        str = formatter.format(getXmin());
//        if (str.endsWith(".0")) {
//            str = str.substring(0, str.length() - 2);
//        }
//        if (str.equals("-0.0") || str.equals("-0")) {
//            str = "0";
//        }
//        if (str.indexOf('.') > -1 && getXmax() > 1) {
//            str = str.substring(0, str.indexOf('.'));
//        }
//        int ln = g.getFontMetrics().stringWidth(str);
//        g.setColor(getIntervalColor());
//        g.drawString(str, (int) (getxAxisStart() - (ln / 2.0)) - 5, gethOffset() + 20);
        g.setColor(getLineColor());
        g.draw(new Line2D.Double(getxAxisStart(), gethOffset(), getxAxisStart(), gethOffset() + 5));
    }
    
    public String getTag() {
		return tag;
	}
    public void setTag(String tag) {
		this.tag = tag;
	}
}
